fix(query): allow rerank on every search operator - #608
Open
dudanogueira wants to merge 3 commits into
Open
Conversation
rerank(Rerank) was declared on BaseVectorSearchBuilder, so only the near* searches could be reranked. BM25, Hybrid and FetchObjects could not, even though rerank is a top-level field of the search request and the server applies it the same way for all of them. Move the option to BaseQueryOptions, which every operator builder extends, and marshal it from BaseQueryOptions.appendTo. The near* records no longer carry their own rerank component; QueryOperator.rerank() now reads it off the common options, so it stays readable on every operator. Closes #603 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WmY5dAGWCccWDoqkKNC2JU
The server returns a rerank score per object (MetadataResult.rerank_score) and per group (GroupByResult.rerank), but neither was read: a reranked search arrived correctly ordered with the number that produced the order missing, and no way to get it short of re-reading the reply. Add rerankScore to QueryMetadata and QueryResponseGroup and populate both. The score is only set when the reply says it is present -- 0.0 is what the dummy reranker returns for a match, so the value alone cannot distinguish "not reranked" from "reranked with score 0". Closes #604 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WmY5dAGWCccWDoqkKNC2JU
The generative search reuses the query operators, so it accepts rerank the same way -- but GenerativeResponseGrouped dropped the group-level score that QueryResponseGrouped reads, leaving that one path unable to tell how the groups were ranked. Per-object scores were already covered: every unmarshal path funnels through QueryResponse.unmarshalResultObject. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WmY5dAGWCccWDoqkKNC2JU
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Infrastructure as Code | View in Orca | ||
| SAST | View in Orca | ||
| Secrets | View in Orca | ||
| Vulnerabilities | View in Orca |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Two independent gaps in the v6 rerank support, both reported against 6.3.1 / Weaviate 1.39.0:
rerank(...)was reachable only from thenear*operators. BM25, Hybrid and FetchObjects could not be reranked through the client, even thoughSearchRequest.rerankis a top-level proto field and the server applies it identically for all operators (the issue includes a hand-marshalled BM25+rerank request whose scores match GraphQL exactly). Java was the only client missing from the "Rerank keyword search results" tab set in the docs.MetadataResult.rerank_score) and per group (GroupByResult.rerank), and the client read neither. A reranked search arrived correctly ordered with the number that produced the order discarded, and no public way to recover it short of re-reading the reply throughGrpcTransportinternals.Approach
#603: move
rerank(Rerank)fromBaseVectorSearchBuilderup toBaseQueryOptions.Builder, which every operator builder extends, and marshal it fromBaseQueryOptions.appendTo. The alternative — duplicating the setter ontoBm25.Builder,Hybrid.BuilderandFetchObjects.Builder— was rejected: rerank is genuinely a common query option, not a per-operator one, and duplication would have kept three more copies of the same marshalling in sync.Consequence: the
near*records no longer carry their ownRerankcomponent. To keepoperator.rerank()readable on every operator rather than only the ones that used to have the component,QueryOperator.rerank()now defaults to reading it offcommon()instead of returningnull.QueryRequest.marshalcorrespondingly drops its separateoperator.rerank().appendTo(...)call, since the common options now marshal it.#604: add
rerankScoretoQueryMetadata(per object) and toQueryResponseGroup/GenerativeResponseGroup(per group). The per-object value is set only whenrerank_score_presentis true —0.0is what the dummy reranker returns for a match, so the value alone cannot distinguish "not reranked" from "reranked with score 0". The type isDouble, notfloat, so absence is representable asnull.Generative search reuses the query operators, so it accepts rerank the same way; per-object scores there were already covered because every unmarshal path funnels through
QueryResponse.unmarshalResultObject. Only the group-level score needed the separate fix inGenerativeResponseGrouped.Key areas for review
BaseQueryOptions.java— the movedrerankcomponent and its marshalling inappendTo; check the record component ordering matches the canonical constructor callQueryOperator.rerank()— now derives fromcommon(); confirm no operator has a non-nullcommon()that could diverge from what it marshalsQueryRequest.marshal— the removed rerank append; make sure nothing marshals rerank twice or not at allQueryResponse.unmarshalResultObject— thegetRerankScorePresent()guard, the whole point of v6: rerank score is never unmarshalled from the search reply #604's edge caseGenerativeResponseGrouped/QueryResponseGrouped—hasRerank()guard for the group scoreRisks and mitigations
QueryRequest.marshalused to append rerank separately; the old path is removed in the same commit that adds the new one, andRerankTest.test_rerankIsMarshalledasserts the marshalled request across six operators.test_rerankScoreZeroIsNotAbsent, which asserts0.0andnullare distinguished within one reply.Testing
Unit tests (no server needed):
RerankTest— parameterised over bm25, hybrid, fetchObjects, nearText, nearVector, nearObject: rerank marshals to the top-level proto field and reads back off the operator. Plus: no rerank by default, rerank without a query (query is optional), score unmarshalling, and the 0.0-vs-absent case.GenerativeRerankTest— group-level score unmarshalled, absent without rerank, and per-object score present on grouped generative results.Integration (
SearchITest,DummyReranker):Rerankand come back with a non-null score on every object.test_rerankScoreIsReturned— a reranked fetchObjects yields a score on every object; the same query without rerank leaves itnull.Locally: full unit suite green (397 tests), and
SearchITestgreen against a real container (33 run, 2 skipped by version gate).Breaking changes
Source/binary compatible for normal builder-based usage, but three public record shapes changed:
NearText,NearVector,NearObject,NearImage,NearAudio,NearVideo,NearDepth,NearThermal,NearImu— theRerank rerankcomponent is gone from the canonical constructor. Thererank()accessor still exists viaQueryOperator's default method.QueryMetadata— new trailingDouble rerankScorecomponent.QueryResponseGroup/GenerativeResponseGroup— newDouble rerankScorecomponent beforeobjects.Anyone calling those canonical constructors directly or destructuring them in a record pattern will need to adjust. Builders and accessors are unaffected.
Closes #603
Closes #604
🤖 Generated with Claude Code
https://claude.ai/code/session_01WmY5dAGWCccWDoqkKNC2JU